﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml.Linq;
using SimpleFormulaParser;
using System.Collections.ObjectModel;
using System.ComponentModel;


namespace MyFilter
{
   
    public class Filter<T> : IFilter, INotifyPropertyChanged
    {
        public Filter(bool isNotRoot)
        {
            Properties = (from p in typeof(T).GetProperties() where _allowedTypes.Contains(p.PropertyType) select new PropertyInfo(p)).ToList<PropertyInfo>();
            Create(isNotRoot);
        }        
            
        public Filter(bool isNotRoot, List<string> propertyList)
        {
            Properties = (from p in typeof(T).GetProperties() where (_allowedTypes.Contains(p.PropertyType) && propertyList.Contains(p.Name)) select new PropertyInfo(p)).ToList<PropertyInfo>();            
            Create(isNotRoot);
        }

        // Property list with aliases
        public Filter(bool isNotRoot, List<PropertyInfo> propertyList)
        {
            //Properties = (from p in typeof(T).GetProperties() where (_allowedTypes.Contains(p.PropertyType) && propertyList.Exists(p1=>p1.PropertyName == p.Name)) select new PropertyInfo(p)).ToList<PropertyInfo>();
            Properties = (from p in typeof(T).GetProperties()
                          join t in _allowedTypes
                          on p.PropertyType equals t
                          join p0 in propertyList
                          on p.Name equals p0.Name
                          select new PropertyInfo(p, p0.PropertyName)).ToList<PropertyInfo>();
            Create(isNotRoot);
        }

        public Filter(XElement xml)
            : this(false, xml, null)
        {
        }

        public Filter(XElement xml, List<string> propertyList)
            : this(false, xml, null, propertyList)
        {
        }

        public Filter(XElement xml, List<PropertyInfo> propertyList)
            : this(false, xml, null, propertyList)
        {
        }        

        private Filter(bool isNotRoot, XElement xml, ParameterExpression prm)
        {
            Properties = (from p in typeof(T).GetProperties() where _allowedTypes.Contains(p.PropertyType) select new PropertyInfo(p)).ToList<PropertyInfo>();
            CreateFromXML(isNotRoot, xml, prm);
        }

        private Filter(bool isNotRoot, XElement xml, ParameterExpression prm, List<string> propertyList)
        {
            Properties = (from p in typeof(T).GetProperties() where (_allowedTypes.Contains(p.PropertyType) && propertyList.Contains(p.Name)) select new PropertyInfo(p)).ToList<PropertyInfo>();            
            CreateFromXML(isNotRoot, xml, prm);
        }


        // Property list with aliases
        private Filter(bool isNotRoot, XElement xml, ParameterExpression prm, List<PropertyInfo> propertyList)
        {
            Properties = (from p in typeof(T).GetProperties()
                          join t in _allowedTypes
                          on p.PropertyType equals t
                          join p0 in propertyList
                          on p.Name equals p0.Name
                          select new PropertyInfo(p, p0.PropertyName)).ToList<PropertyInfo>();

            //Properties = (from p in typeof(T).GetProperties() where (_allowedTypes.Contains(p.PropertyType) && propertyList.Exists(p1 => p1.PropertyName == p.Name))                           
            //              select new PropertyInfo(p)).ToList<PropertyInfo>();
            CreateFromXML(isNotRoot, xml, prm);
        }

        private void Create(bool isNotRoot)
        {
            IsNotRoot = isNotRoot;

            // "And" by default
            AndOrIdx = 0;
            CmpExpressions = new ObservableCollection<CmpExpression>();
            CmpExpressions.Clear();

            if (!isNotRoot)
                Prm = Expression.Parameter(typeof(T), "T");

            Root = this;
            while (Root.Parent != null)
                Root = (Filter<T>)Root.Parent;
        }

        private ParameterExpression Prm;
        Filter<T> Root;

        List<Type> _allowedTypes = new List<Type>() { typeof(int), typeof(long), typeof(Single), typeof(double), typeof(float), typeof(string), typeof(DateTime), typeof(bool) };


        private void CreateFromXML(bool isNotRoot, XElement xml, ParameterExpression prm)
        {
            IsNotRoot = isNotRoot;

            // "And" by default
            //AndOrIdx = 0;

            CmpExpressions = new ObservableCollection<CmpExpression>();
            CmpExpressions.Clear();
            int i = 0;
            if (!isNotRoot)
                Prm = Expression.Parameter(typeof(T), "T");
            else
                Prm = prm;

            Root = this;
            while (Root.Parent != null)
                Root = (Filter<T>)Root.Parent;

            AndOrIdx = Convert.ToInt32(xml.Attribute("AndOr").Value);
            foreach (XElement xElement in xml.Elements("CmpExpression"))
            {
                CmpExpression cmpExpression;
                string name = xElement.Attribute("Name").Value;
                if (xElement.Attribute("IsSimple").Value == "1")
                {
                    PropertyInfo pInfo = Properties.First(p => p.Name == name);
                    cmpExpression = new CmpExpression()
                    {
                        ID = i,
                        BinaryExprList = (pInfo.PropertyType == typeof(string)) ? this.BinaryExprListForStrings : this.BinaryExprList,
                        IsSimpleExpression = true,
                        Name = name,
                        Not = (xElement.Attribute("Not").Value == "1") ? (true) : (false),
                        ExpType = Convert.ToInt32(xElement.Attribute("ExpType").Value),
                        CmpValue = xElement.Attribute("CmpValue").Value,
                        PropertyType = pInfo.PropertyType,
                        BinaryExpressions = this.BinaryExpressions,
                        SelectedExprIndex = Convert.ToInt32(xElement.Attribute("ExpIdx").Value)
                    };
                }
                else
                {
                    Filter<T> filter = new Filter<T>(true, xElement, Prm);
                    filter.Parent = this;
                    cmpExpression = new CmpExpression()
                    {
                        ID = i,
                        ExpType = 1,
                        Not = (xElement.Attribute("Not").Value == "1") ? (true) : (false),
                        IsSimpleExpression = false,
                        Name = name,
                        Filter = filter
                    };
                }

                CmpExpressions.Add(cmpExpression);
                i++;
            }
        }

        public bool IsNotRoot
        { set; get; }

        public int AndOrIdx
        { set; get; }


        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        bool _IncorrectInput;
        public bool IncorrectInput
        { set
        {
            _IncorrectInput = value;
            OnPropertyChanged(new PropertyChangedEventArgs("IncorrectInput"));
        }
           get {return _IncorrectInput;} 
        }

        public List<PropertyInfo> Properties
        { set; get; }

        public ObservableCollection<CmpExpression> CmpExpressions
        { set; get; }

        public List<string> BinaryExprList = new List<string>() { "=", "!=", ">", ">=", "<", "<=" };

        public List<string> BinaryExprListForStrings = new List<string>() { "=", "!=", ">", ">=", "<", "<=", "Contains", "Begins with", "Ends with" };

        IFilter _Parent;
        public IFilter Parent
        { set {            
            if (value!=null)
            {
                Filter<T> filter = (Filter<T>)value;
                Prm = filter.Prm;
                Root = filter.Root;
            }
            
            _Parent = value;
             } get { return _Parent; } }

        public CmpExpression ParentCmpExpression
        { set; get; }

        private List<Func<Expression, Expression, BinaryExpression>> AndOrExprsns = new List<Func<Expression, Expression, BinaryExpression>>()
        {
            Expression.And,
            Expression.Or
        };

        private List<Func<Expression, Expression, BinaryExpression>> BinaryExpressions =
            new List<Func<Expression, Expression, BinaryExpression>>()
            {
            Expression.Equal,
            Expression.NotEqual,
            Expression.GreaterThan,
            Expression.GreaterThanOrEqual,            
            Expression.LessThan,
            Expression.LessThanOrEqual
            };

        private string[] AndOr = new string[] { " AND ", " OR " };

        private int GenPropID()
        {
            int i = CmpExpressions.Count;
            while (CmpExpressions.Any(cp=>cp.ID == i))
                i++;
            return i;
        }

        Expression GenExpr(CmpExpression cp)
        {
            Expression exp1;
            
            if (cp.IsCompoundExpression)
                exp1 = ((Filter<T>)cp.Filter).BuildSubExpression();
            else
            {
                if (cp.PropertyType == typeof(string))
                    exp1 = CmpStrings(cp);
                else
                    if (cp.PropertyType == typeof(bool))
                        exp1 = CmpBools(cp);
                    else
                        exp1 = CmpNumbers(cp);
            }

            if (cp.Not)
                exp1 = Expression.Not(exp1);
            
            return exp1;
        }

        /// <summary>
        /// Строим выражение
        /// </summary>
        /// <returns></returns>
        
        public Expression<Func<T, bool>> BuildExpression()
        {
            Filter<T> filter = this;
            while (filter.Parent != null)
                filter = (Filter<T>)filter.Parent;
            //return filter.BuildSubExpression();

            return Expression.Lambda<Func<T, bool>>(filter.BuildSubExpression(), new ParameterExpression[] { Prm });           
        }

        public Expression<Func<T, bool>> TryBuildExpression()
        {
            try
            {
                Filter<T> filter = this;
                while (filter.Parent != null)
                    filter = (Filter<T>)filter.Parent;
                //return filter.BuildSubExpression();                
                Expression<Func<T, bool>> exp = Expression.Lambda<Func<T, bool>>(filter.BuildSubExpression(), new ParameterExpression[] { Prm });

                if (filter.IncorrectInput)
                    return null;
                else
                    return exp;
            }
            catch
            {
            }
            
            return null;            
        }

        public Expression BuildSubExpression()
        {            
            Expression exp;
            int j = 0;

            CmpExpression cp;
            IncorrectInput = false;

            while ((j < CmpExpressions.Count) && (!CmpExpressions[j].IsCompoundExpression) && (CmpExpressions[j].CmpValue.Length == 0))
                j++;

            if (j < CmpExpressions.Count)
            {
                exp = GenExpr(CmpExpressions[j]);
                j++;
            }
            else
                return Expression.Constant(true, typeof(bool));

            Expression exp1;
            for (int i = j; i < CmpExpressions.Count; i++)
            {
                cp = CmpExpressions[i];
                if ((cp.IsCompoundExpression) || (cp.CmpValue.Length > 0))
                {
                    exp1 = GenExpr(cp);
                    exp = AndOrExprsns[AndOrIdx](exp, exp1);
                }
            }
            return exp;
        }
      

        /// <summary>
        /// Создает выражение для сравнения числового свойства с константой или другим свойством
        /// </summary>
        /// <param name="expIdx">Номер выражения сравнения</param>
        /// <param name="obj">Параметр выражения (Экземпляр класса T)</param>
        /// <param name="cp"></param>
        /// <returns></returns>
        Expression CmpNumbers(CmpExpression cp)
        {
            try
            {
                int expIdx = cp.SelectedExprIndex;

                Expression tmpExp;
                if (cp.PropertyType == typeof(DateTime))
                {
                    tmpExp = cp.BinaryExpressions[expIdx](
                            Expression.Property(Prm, cp.Name),
                            Expression.Constant(DateTime.ParseExact(cp.CmpValue, "yyyy-MM-dd", null)));                    
                }
                else
                {
                    IFormulaExp formulaExp = FormulaExpFactory.CreateFormulaExp<T>(cp.PropertyType);
                    formulaExp.Prm = Prm;
                    tmpExp = cp.BinaryExpressions[expIdx](
                            Expression.Property(Prm, cp.Name),
                            formulaExp.ParseString(cp.CmpValue));
                }                

                
                cp.IsCorrectValue = true;
                return tmpExp;
            }
            catch 
            {
                cp.IsCorrectValue = false;
                IncorrectInput = true;
                Root.IncorrectInput = true;
                return Expression.Constant(true);
            }
        }

        Expression CmpBools(CmpExpression cp)
        {
            int expIdx = 1 - Convert.ToInt32(cp.CmpValue);
            return cp.BinaryExpressions[expIdx](Expression.Property(Prm, cp.Name), Expression.Constant(true, typeof(bool)));
        }

	    // Сравнение строк
        Expression CmpStrings(CmpExpression cp)
        {
            int expIdx = cp.SelectedExprIndex;
            if (expIdx < 6)
            {
                return cp.BinaryExpressions[expIdx](
                                    Expression.Call(Expression.Property(Prm, cp.Name), typeof(String).GetMethod("CompareTo", new Type[] { typeof(string) }),
                                        new Expression[] { Expression.Constant(Convert.ChangeType(cp.CmpValue, cp.PropertyType), cp.PropertyType) }),
                                    Expression.Constant(0));
            }
            else
            {
                string expName = (expIdx == 6) ? "Contains" : ((expIdx == 7) ? "StartsWith" : "EndsWith");
                return Expression.Equal(
                                Expression.Call(Expression.Property(Prm, cp.Name), typeof(string).GetMethod(expName, new Type[] { typeof(string) }),
                                    new Expression[] { Expression.Constant(Convert.ChangeType(cp.CmpValue, cp.PropertyType), cp.PropertyType) }),
                                Expression.Constant(true));
            }
        }

        public void AddSimpleExpression(CmpExpression c)
        {            
            int idx = CmpExpressions.IndexOf(c);
            if (idx < 0)
                idx = 0;
            CmpExpressions.Insert(idx + 1,
                new CmpExpression
                {
                    ID = GenPropID(),
                    ExpType = CmpExpressions[idx].ExpType,
                    IsSimpleExpression = true,
                    Name = CmpExpressions[idx].Name,
                    BinaryExprList = CmpExpressions[idx].BinaryExprList,
                    BinaryExpressions = CmpExpressions[idx].BinaryExpressions,
                    PropertyType = CmpExpressions[idx].PropertyType,
                    CmpValue = CmpExpressions[idx].CmpValue,
                    SelectedExprIndex = CmpExpressions[idx].SelectedExprIndex
                }
            );  
        }

        public void AddSimpleExpression(PropertyInfo pInfo)
        {
            if (pInfo == null)
                return;
            CmpExpressions.Add(
                new CmpExpression
                {
                    ID = GenPropID(),
                    IsSimpleExpression = true,
                    ExpType = (pInfo.PropertyType == typeof(bool)) ? 2 : 4,
                    Name = pInfo.Name,                   
                    CmpValue = "",
                    PropertyType = pInfo.PropertyType,
                    BinaryExpressions = this.BinaryExpressions,
                    BinaryExprList = (pInfo.PropertyType == typeof(string)) ? this.BinaryExprListForStrings : this.BinaryExprList,
                    SelectedExprIndex = 0                    
                }
            );            
        }

        public IFilter GetSubFilter(int pID)
        {
            int idx = CmpExpressions.ToList<CmpExpression>().FindIndex(p => ((!p.IsSimpleExpression) && (p.ID == pID)));            
            if (idx < 0)
                return this;
            return CmpExpressions[idx].Filter;
        }

        public void AddCompoundExpression()
        {
            CmpExpression ccp = new CmpExpression()
            {
                ID = GenPropID(),
                ExpType = 1,
                IsSimpleExpression = false,               
                Filter = new Filter<T>(true)
            };
            ccp.Filter.Parent = (IFilter)this;
            CmpExpressions.Add(ccp);            
        }

        public void AddCompoundExpression(int idx, IFilter filter)
        {
            CmpExpression ccp = new CmpExpression()
            {
                ID = GenPropID(),
                ExpType = 1,
                IsSimpleExpression = false,                
                Filter = filter
            };
            ccp.Filter.Parent = this;
            CmpExpressions.Insert(idx, ccp);
            
        }

        public void RemoveExpression(CmpExpression c)
        {
            CmpExpressions.Remove(c);            
        }

        //public void RemoveExpression(int pID)
        //{
        //    int idx = CmpExpressions.FindIndex(p => p.ID == pID);
        //    if (idx >= 0)
        //        CmpExpressions.RemoveAt(idx);
        //    OnPropertyChanged(new PropertyChangedEventArgs("CmpExpressions"));
        //}

        public IFilter PutIntoBrackets()
        {
            Filter<T> filter = new Filter<T>(this.IsNotRoot);
            filter.Parent = this.Parent;
            filter.Prm = this.Prm;
            this.Parent = filter;
            this.IsNotRoot = true;
            if (this.ParentCmpExpression!=null)
                this.ParentCmpExpression.Filter = filter;
            filter.AddCompoundExpression(0, this);
            return filter; 
        }

        public XElement GetXML(string name, bool not)
        {
            XElement xml = new XElement("CmpExpression", new XAttribute("IsSimple", "0"), new XAttribute("AndOr", AndOrIdx.ToString())
                , new XAttribute("Name", name), new XAttribute("Not", (not ? "1" : "0")));
            XElement exXml;
            foreach (CmpExpression cp in CmpExpressions)
            {
                if ((cp.IsCompoundExpression) || (cp.CmpValue.Length > 0))
                {
                    if (cp.IsCompoundExpression)
                        exXml = cp.Filter.GetXML(cp.Name, cp.Not);
                    else
                    {                        
                        exXml = new XElement("CmpExpression"                            
                            , new XAttribute("IsSimple", "1")
                            , new XAttribute("Not", (cp.Not ? "1" : "0"))
                            , new XAttribute("Name", cp.Name)
                            , new XAttribute("ExpIdx", cp.SelectedExprIndex.ToString())
                            , new XAttribute("ExpType", cp.ExpType.ToString())
                            , new XAttribute("CmpValue", cp.CmpValue)
                            , new XAttribute("PropertyType", cp.PropertyType.ToString()));
                    }
                    xml.Add(exXml);
                }
            }
            return xml;
        }

        public XElement GetXMLRoot()
        {
            Filter<T> filter = this;
            while (filter.Parent != null)
                filter = (Filter<T>)filter.Parent;
            return filter.GetXML("Root", false);
        }

        public string SubQueryText()
        {
            if (CmpExpressions.Count == 0)
                return "TRUE";

            string subQueryText = "";
            CmpExpression cp;

            int j = 0;
            while ((j < CmpExpressions.Count) && (!CmpExpressions[j].IsCompoundExpression) && (CmpExpressions[j].CmpValue.Length == 0))
                j++;
                    
            if (j<CmpExpressions.Count)
            {
                cp = CmpExpressions[j];
                if (cp.IsCompoundExpression)
                    subQueryText += (cp.Not ? " NOT " : "") + "(" + cp.Filter.SubQueryText() + ")";
                else
                    if (cp.CmpValue.Length > 0)
                    {
                        string qs = (cp.SelectedExprIndex > 5) ? "'" : "";
                        subQueryText += (cp.Not ? " NOT " : "") + "(" + cp.Name + " " + BinaryExprListForStrings[cp.SelectedExprIndex] + " " + qs + cp.CmpValue + qs + ")";
                    }                

                for (int i = j+1; i < CmpExpressions.Count; i++)
                {
                    cp = CmpExpressions[i];

                    if (cp.IsCompoundExpression)
                        subQueryText += AndOr[AndOrIdx] + (cp.Not ? " NOT " : "") + "(" + cp.Filter.SubQueryText() + ")";
                    else
                        if (cp.CmpValue.Length > 0)                    
                        {
                            string qs = (cp.SelectedExprIndex > 5) ? "'" : "";
                            subQueryText += AndOr[AndOrIdx] + (cp.Not ? " NOT " : "") + "(" + cp.Name + " " + 
                                BinaryExprListForStrings[cp.SelectedExprIndex] + " " + qs + cp.CmpValue + qs + ")";
                        }
                    }
                }            
            return subQueryText;
        }

        public string QueryText()
        {
            Filter<T> filter = this;
            while (filter.Parent != null)
                filter = (Filter<T>)filter.Parent;
            return filter.SubQueryText();           
        }        
    }

    public class CmpExpression : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }        

        public int ID
        { set; get; }

        public bool IsCompoundExpression
        {
            get
            {
                return !IsSimpleExpression;
            }
        }

        public int ExpType
        { set; get; }

        public bool Not
        { set; get; }

        private bool _isCorrectValue = true;
        public bool IsCorrectValue
        {
            set
            {
                _isCorrectValue = value;
                OnPropertyChanged(new PropertyChangedEventArgs("IsCorrectValue"));
            }
            
            get {return _isCorrectValue;}
        }

        private bool _IsSimpleExpression;
        public bool IsSimpleExpression
        { set { if (!value) ExpType = (ExpType | 1); _IsSimpleExpression = value; } get { return _IsSimpleExpression; } }

        private IFilter _Filter;
        public IFilter Filter
        { set { value.ParentCmpExpression = this; _Filter = value; } get { return _Filter; } }
        string _Name;
        public string Name
        {
            set { _Name = value; }
            get
            {
                if (IsSimpleExpression)
                    return _Name;
                else return Filter.SubQueryText();
            }
        }
        public string CmpValue
        { set; get; }
        
        public Type PropertyType
        { set ;  get ;}
        public List<Func<Expression, Expression, BinaryExpression>> BinaryExpressions
        { set; get; }
        public List<string> BinaryExprList
        { set; get; }
        public int SelectedExprIndex 
        { set; get; }
    }

    public interface IFilter
    {
        int AndOrIdx
        { set; get; }
        List<PropertyInfo> Properties
        { set; get; }
        IFilter Parent
        { set; get; }
        bool IncorrectInput
        { set; get; }
        ObservableCollection<CmpExpression> CmpExpressions
        { set; get; }
        CmpExpression ParentCmpExpression
        { set; get; }
        void AddCompoundExpression();
        void AddCompoundExpression(int idx, IFilter filter);
        void AddSimpleExpression(PropertyInfo pInfo);
        void AddSimpleExpression(CmpExpression c);
        void RemoveExpression(CmpExpression c);        
        IFilter PutIntoBrackets();
        IFilter GetSubFilter(int pID);
        string QueryText();
        string SubQueryText();
        XElement GetXML(string name, bool not);
        XElement GetXMLRoot();
    }

    public class PropertyInfo
    {
        public PropertyInfo(System.Reflection.PropertyInfo pInfo)
        {
            PropertyType = pInfo.PropertyType;
            Name = pInfo.Name;
            PropertyName = pInfo.Name;
        }

        public PropertyInfo(System.Reflection.PropertyInfo pInfo, string name)
        {
            PropertyType = pInfo.PropertyType;
            Name = pInfo.Name;
            PropertyName = name;
        }
        
        public PropertyInfo(string propertyName, string name)
        {            
            Name = propertyName;
            PropertyName = name;
        }

        public Type PropertyType
        { set; get; }
        public string Name
        { set; get; }
        public string PropertyName
        { set; get; }

    }  
}
